import { ALL, Controller, Get, Post, Param, Inject, Provide, Body, Validate } from '@midwayjs/decorator'

import { BaseController } from './base'
import { TicketService } from '../service/ticket'
import { UploadService } from '../service/upload'
import { TemplateService } from '../service/template'
import { TicketDTO, TicketUpdateDTO } from '../dto/ticket'
import { CODE, MESSAGE } from '../constants/code'

import {
  getBookmarks,
  getDocxEditor,
  createImageXML,
  createImageRelationXML
} from '../utils/docx-editor'
import {
  imageClipper
} from '../utils/image-clipper'

const _ = require('lodash')

@Provide()
@Controller('/api/system/ticket')
export class TicketController extends BaseController {

  @Inject()
  ticketService: TicketService

  @Inject()
  uploadService: UploadService

  @Inject()
  templateService: TemplateService

  @Get('/:id')
  async get(@Param() id: string) {
    const ticket = await this.ticketService.get(id)

    if (!ticket) {
      return this.notOk(CODE.NOT_FOUND)
    }

    return this.ok(ticket)
  }

  @Post('/')
  @Validate()
  async add(@Body(ALL) ticket: TicketDTO) {
    const { id, is_system_user } = this.ctx.state

    if (!id || !is_system_user) {
      return this.notOk(CODE.NOT_ALLOWED)
    }

    const addedTicket = await this.ticketService.add({
      ...ticket,
      create_by_id: id
    })

    if (!addedTicket) {
      return this.notOk(CODE.FAIL)
    }

    return this.ok(addedTicket)
  }

  @Post('/:id/update')
  @Validate()
  async update(
    @Param('id') id: string,
    @Body(ALL) ticket: TicketUpdateDTO
  ) {
    const { id: user_id, is_system_user } = this.ctx.state

    if (!user_id || !is_system_user) {
      return this.notOk(CODE.NOT_ALLOWED)
    }

    const existed = await this.ticketService.get(id)

    if (!existed) {
      return this.notOk(CODE.NOT_FOUND)
    }

    if (this.ticketService.STATUS[ticket.status] <= existed?.status) {
      return this.notOk(CODE.NOT_ALLOWED)
    }

    ticket['id'] = id

    if (~['CANCELLED', 'PENDING'].indexOf(ticket.status)) {
      ticket['accept_by_id'] = user_id
      ticket['accept_at'] = 'now()'
    }

    const updated = await this.ticketService.update(ticket)

    if (!updated) {
      return this.notOk(CODE.FAIL)
    }

    return this.ok(updated)
  }

  @Post('/:id/upload')
  async upload(
    @Param('id') id: string
  ) {
    const { id: user_id, is_system_user } = this.ctx.state

    if (!user_id || !is_system_user) {
      return this.notOk(CODE.NOT_ALLOWED)
    }

    const ticket = await this.ticketService.get(id)

    if (ticket.status !== this.ticketService.STATUS.PENDING) {
      return this.notOk(CODE.NOT_ALLOWED)
    }

    const stream = await this.ctx.getFileStream()
    const upload = await this.uploadService.upload(stream)

    if (upload?.id) {
      const returning = await this.ticketService.addPending({
        attachment_id: upload.id,
        ticket_id: id
      })

      if (returning) {
        return this.ok(returning)
      }
    }
  }

  @Post('/:id/upload/sign')
  async uploadSign(
    @Param('id') id: string
  ) {
    const { id: user_id, is_system_user } = this.ctx.state

    if (!user_id || !is_system_user) {
      return this.notOk(CODE.NOT_ALLOWED)
    }

    const stream = await this.ctx.getFileStream()
    const upload = await this.uploadService.upload(stream)

    if (upload?.id) {
      const buffer = await this.uploadService.getBuffer(upload.id)
      const bookmarks = await getBookmarks(buffer)

      const returning = await this.ticketService.addSign({
        attachment_id: upload.id,
        ticket_id: id,
        bookmarks: _.uniq(bookmarks)
      })

      if (returning) {
        return this.ok(returning)
      }
    }
  }

  @Get('/:id/sign/:attachmentId')
  async downloadSign(
    @Param('id') id: string,
    @Param('attachmentId') attachmentId: string
  ) {
    if (!id || !attachmentId) {
      return this.ctx.throw(404, this.ctx.__(MESSAGE.MISSING_PARAM))
    }

    const ticket = await this.ticketService.get(id)
    const attachment = await this.uploadService.get(attachmentId)

    if (!ticket || !attachment) {
      return this.ctx.throw(404, this.ctx.__(MESSAGE.NOT_FOUND))
    }

    const { bookmarks } = ticket?.bookmarks.find(o => o.id === attachmentId) || {}

    if (!Array.isArray(bookmarks)) {
      return this.ctx.throw(404, this.ctx.__(MESSAGE.NOT_FOUND))
    }

    const replacements = []
    const toReplace = bookmarks.filter(b => b.filepath !== null)

    const buffer = await this.uploadService.getBuffer(attachmentId)
    const docEditor = await getDocxEditor(buffer)

    docEditor.mkdir('word/media')

    let start = 1000

    for (const replacement of toReplace) {
      const { filepath, name } = replacement

      if (filepath) {
        const imgBuffer = await this.uploadService.getBuffer(filepath)
        const { buffer: imgClippedBuffer, w, h } = await imageClipper(imgBuffer)

        // store image
        docEditor.writeFile(`word/media/${start}.png`, imgClippedBuffer)

        replacements.push({
          relationship: createImageRelationXML(start, `media/${start}.png`),
          xml: createImageXML(start, w, h),
          bookmark: name
        })

        start += 1
      }
    }

    // insert image relations
    docEditor.parse('word/_rels/document.xml.rels', xml => {
      const relations = xml[0]['Relationships']

      relations.push(...replacements.map(({ relationship }) => relationship))
    })

    // insert image xml
    docEditor.parse('word/document.xml', xml => {
      const body = xml[0]['w:document'][0]['w:body']

      body.forEach((item) => {
        if (Array.isArray(item['w:p'])) {
          const inserting = []

          item['w:p'].forEach((subItem, index) => {
            if (Array.isArray(subItem['w:bookmarkStart'])) {
              if (
                subItem[':@'] &&
                subItem[':@']['@_w:name'] !== '_GoBack'
              ) {
                const matched = replacements.find(
                  o => o.bookmark === subItem[':@']['@_w:name']
                )

                if (matched) {
                  inserting.push({ index, xml: matched.xml })
                }
              }
            }
          })

          if (inserting.length) {
            for (let i = inserting.length - 1; i >= 0; i -= 1) {
              const { index, xml: imageXML } = inserting[i]

              item['w:p'].splice(index, 0, imageXML)
            }
          }
        }
      })
    })

    const { filename: rawFilename, ext } = attachment
    const filename = rawFilename.replace(ext, '') + '-已签名' + ext

    this.ctx.attachment(filename)
    this.ctx.set('Content-Type', 'application/octet-stream')
    this.ctx.body = docEditor.stream()
  }

  @Get('/:id/template/:tid')
  async download(
    @Param('id') id: string,
    @Param('tid') tid: number
  ) {
    if (!id || !tid) {
      return this.ctx.throw(404, this.ctx.__(MESSAGE.MISSING_PARAM))
    }

    const ticket = await this.ticketService.get(id)
    const template = await this.templateService.get(tid)

    if (!ticket || !template) {
      return this.ctx.throw(404, this.ctx.__(MESSAGE.NOT_FOUND))
    }

    const { bookmarks } = ticket?.bookmarks.find(o => o.id === Number(tid)) || {}

    if (!Array.isArray(bookmarks)) {
      return this.ctx.throw(404, this.ctx.__(MESSAGE.NOT_FOUND))
    }

    const replacements = []
    const toReplace = bookmarks.filter(b => b.filepath !== null)

    const buffer = await this.uploadService.getBuffer(template?.attachment?.id)
    const docEditor = await getDocxEditor(buffer)

    docEditor.mkdir('word/media')

    let start = 1000

    for (const replacement of toReplace) {
      const { filepath, name } = replacement

      if (filepath) {
        const imgBuffer = await this.uploadService.getBuffer(filepath)
        const { buffer: imgClippedBuffer, w, h } = await imageClipper(imgBuffer)

        // store image
        docEditor.writeFile(`word/media/${start}.png`, imgClippedBuffer)

        replacements.push({
          relationship: createImageRelationXML(start, `media/${start}.png`),
          xml: createImageXML(start, w, h),
          bookmark: name
        })

        start += 1
      }
    }

    // insert image relations
    docEditor.parse('word/_rels/document.xml.rels', xml => {
      const relations = xml[0]['Relationships']

      relations.push(...replacements.map(({ relationship }) => relationship))
    })

    // insert image xml
    docEditor.parse('word/document.xml', xml => {
      const body = xml[0]['w:document'][0]['w:body']

      body.forEach((item) => {
        if (Array.isArray(item['w:p'])) {
          const inserting = []

          item['w:p'].forEach((subItem, index) => {
            if (Array.isArray(subItem['w:bookmarkStart'])) {
              if (
                subItem[':@'] &&
                subItem[':@']['@_w:name'] !== '_GoBack'
              ) {
                const matched = replacements.find(
                  o => o.bookmark === subItem[':@']['@_w:name']
                )

                if (matched) {
                  inserting.push({ index, xml: matched.xml })
                }
              }
            }
          })

          if (inserting.length) {
            for (let i = inserting.length - 1; i >= 0; i -= 1) {
              const { index, xml: imageXML } = inserting[i]

              item['w:p'].splice(index, 0, imageXML)
            }
          }
        }
      })
    })

    const { filename: rawFilename, ext } = template?.attachment
    const filename = rawFilename.replace(ext, '') + '-已签名' + ext

    this.ctx.attachment(filename)
    this.ctx.set('Content-Type', 'application/octet-stream')
    this.ctx.body = docEditor.stream()
  }
}